import { MINIGAME } from 'cc/env'
import IService from './IService'
import ServiceListener from './ServiceListener'
import { IHanderMessage, IRequestData, IResponseData, IWorkerImpl } from './WokerTypes'

function getCurrentUrl (): string {
  return import.meta.url
}

function createWorker (path: string): Worker {
  if (MINIGAME) {
    const g = window as any
    const sdk = g['wx']
    return sdk.createWorker(path)
  } else {
    return new Worker(path)
  }
}

export default class WorkerSevices<T> extends ServiceListener<T> implements IService<T> {
    private _inited: boolean = false

    private _worker: Worker
    private _impl: IWorkerImpl<T>
    private _seqId: number = 0

    private _initSuccess: ()=>void
    private _initFail: ()=>void

    constructor (impl?: IWorkerImpl<T>) {
      super()
      this._impl = impl
    }

    Init (implUrl: string, data?: any, tranfers?:Transferable[]): Promise<void> {
      this._worker = createWorker('./worker.js')
      this._worker.onmessage = this.onReceivedMessage.bind(this)
      return new Promise((resolve, reject) => {
        this._initSuccess = resolve
        this._initFail = reject
        let origin = window.location.origin
        const pathname = window.location.pathname
        if (pathname.indexOf('/index.html') > -1) {
            origin += pathname.substring(0, pathname.lastIndexOf('/'))
        }
        const initData = {
            context: {
                origin,
                serviceUrl: getCurrentUrl(),
                implUrl,
            },
            data
        }
        this._send('init' as any, initData, undefined, tranfers)
      })
    }

    get IsValid (): boolean {
      return this._inited
    }

    request<TRsp, TReq> (cmd: T, data?: TReq): Promise<TRsp> {
      if (!this.IsValid) return new Promise<TRsp>((_, reject) => {
        reject('not inited!')
      })
      return new Promise<TRsp>((resolve) => {
        const seq = this._nextSeq()
        this.onceMessage(cmd, seq, resolve)
        this._send(cmd, data, seq)
      })
    }

    send<TReq> (cmd: T, data?: TReq): void {
      if (!this.IsValid) return
      return this._send(cmd, data)
    }

    private _nextSeq (): number {
      return this._seqId++
    }

    private _send (cmd: T, data?: any, seq?: number, tranfers?:Transferable[]): void {
      seq = seq || this._nextSeq()
      this._worker.postMessage({cmd, seq, data}, tranfers)
    }

    private onReceivedMessage (event: MessageEvent): void {
      const data: IResponseData<T> = event.data
      const cmd = data.cmd
      if (cmd == 'init') {
        if (data.data) {
          console.log('init sucess!')
          this._inited = true
          this._initSuccess && this._initSuccess()
        } else {
          console.log('init fail!')
          this._initFail && this._initFail()
        }
      } else {
        this.handleMessage(cmd, data.data, data.seq)
      }
    }

    // called in worker
    private initWorker (self: Worker, context: any, data?: any): void {
      console.log(' init worker in WorkerService>>>>>>>>>>>>>>>>>>>> ', context, data)
      return this._impl.initWorker(self, context, data)
    }

    private handleWorkMessage (context: any, data: IRequestData<T>): Promise<IHanderMessage<T>> {
      console.log('>>>>>>>>>>>>>>>>>>>> ', context, data)
      return new Promise((resolve) => {
        this._impl.handleWork(context, data.cmd, data.data).then((resp) => {
          resolve({ isResponse: resp.isResponse, resp: { cmd: data.cmd, seq: data.seq, data: resp.data }, tranfers: resp.tranfers })
        })
      })
    }
}